﻿using System;

namespace DDDProjectSample.Domain
{
    /// <summary>
    /// Abstract base class for all domain object classes.
    /// </summary>
    public abstract class Entity
    {
        private long _id = Int64.MinValue;
        private int? _oldHashCode;

        /// <summary>
        /// Gets the domain object's id (surrogate key)
        /// </summary>
        public virtual long Id
        {
            get { return _id; }
        }

        /*
         * The default implementation of the Equals function is to be found in the System.Object class. 
         * From this class all other classes in .NET implicitly or explicitly inherit. When overriding 
         * the Equals function we have to also override the GetHashCode function.
         * 
         * We also override the two operators '==' and '!=' to make it possible to compare two instances 
         * with those operators instead of only the Equals method.
         */

        public override bool Equals(object obj)
        {
            /*
             * We have to distinguish 3 possible cases. The first one is that the user/developer wants to 
             * compare two objects of different type. This case is trivial : the answer is always 
             * "not equal". The second case is when the two objects are both new (also called transient) 
             * then the two references point to the same instance. And the third case just takes the 
             * implementation of the Equals method of the Int64 type to check for equality.
             */

            Entity other = obj as Entity;
            if (other == null)
                return false;

            // handle the case of comparing two new objects
            bool otherIsTransient = Equals(other.Id, long.MinValue);
            bool thisIsTransient = Equals(Id, long.MinValue);
            if (otherIsTransient && thisIsTransient)
                return ReferenceEquals(other, this);

            return other.Id.Equals(Id);
        }

        public override int GetHashCode()
        {
            /*
             * An object should never ever change it's hash code during its life, that is from the moment the 
             * object is instantiated until it is disposed. If a object is restored from database there is no 
             * problem since any existing database record has always a well defined and unique identity field. 
             * Thus we can derive the hash code from this Id field. This is done in the last line of code in 
             * this function.
             * 
             * A little bit more problematic is the case when a new object is created in memory, then it's 
             * identity field is undefined (the object has not been saved to the database so far and is thus 
             * considered as being transient). In our case, undefined means that the Id field has a value of 
             * "Int64.MinValue". In this case we take the default implementation (of System.Object) of the 
             * GetHashCode method to generate a hash code. But we store it in an instance variable for further 
             * reference.
             * 
             * Later in the life cycle of the instance, it may be persisted to the database (but still continues 
             * to sit around in the memory). At this moment a new unique value is assigned to the Id field of 
             * the instance. Now the object isn't transient any more but the 2 first lines in the method avoid 
             * that the hash code of the object changes. It is still the same object as before. It has just 
             * been made persistent.
             */

            // Once we have a hash code we'll never change it
            if (_oldHashCode.HasValue)
                return _oldHashCode.Value;

            bool thisIsTransient = Equals(Id, long.MinValue);

            // When this instance is transient, we use the base GetHashCode()
            // and remember it, so an instance can never change its hash code.
            if (thisIsTransient)
            {
                _oldHashCode = base.GetHashCode();
                return _oldHashCode.Value;
            }

            return Id.GetHashCode();
        }

        public static bool operator ==(Entity x, Entity y)
        {
            return Equals(x, y);
        }

        public static bool operator !=(Entity x, Entity y)
        {
            return !(x == y);
        }
    }
}
